﻿using System;

namespace Gmantis.Zip.ZLib
{
    internal class Deflate
    {
        // Fields
        private short bi_buf;
        private int bi_valid;
        private const int BL_CODES = 0x13;
        internal short[] bl_count = new short[0x10];
        private Tree bl_desc = new Tree();
        private short[] bl_tree = new short[0x4e];
        private int block_start;
        private const int BlockDone = 1;
        private const int Buf_size = 0x10;
        private const int BUSY_STATE = 0x71;
        private static Config[] config_table = new Config[] { new Config(0, 0, 0, 0, 0), new Config(4, 4, 8, 4, 1), new Config(4, 5, 0x10, 8, 1), new Config(4, 6, 0x20, 0x20, 1), new Config(4, 4, 0x10, 0x10, 2), new Config(8, 0x10, 0x20, 0x20, 2), new Config(8, 0x10, 0x80, 0x80, 2), new Config(8, 0x20, 0x80, 0x100, 2), new Config(0x20, 0x80, 0x102, 0x400, 2), new Config(0x20, 0x102, 0x102, 0x1000, 2) };
        private int d_buf;
        private const int D_CODES = 30;
        private Tree d_desc = new Tree();
        private byte data_type;
        private const int DEF_MEM_LEVEL = 8;
        internal byte[] depth = new byte[0x23d];
        private short[] dyn_dtree = new short[0x7a];
        private short[] dyn_ltree = new short[0x47a];
        private const int DYN_TREES = 2;
        private const int END_BLOCK = 0x100;
        private const int FAST = 1;
        private const int FINISH_STATE = 0x29a;
        private const int FinishDone = 3;
        private const int FinishStarted = 2;
        private int good_match;
        private int hash_bits;
        private int hash_mask;
        private int hash_shift;
        private int hash_size;
        private short[] head;
        internal int[] heap = new int[0x23d];
        internal int heap_len;
        internal int heap_max;
        private const int HEAP_SIZE = 0x23d;
        private const int INIT_STATE = 0x2a;
        private int ins_h;
        private int l_buf;
        private const int L_CODES = 0x11e;
        private Tree l_desc = new Tree();
        private int last_eob_len;
        private int last_flush;
        private int last_lit;
        private const int LENGTH_CODES = 0x1d;
        private int level;
        private int lit_bufsize;
        private const int LITERALS = 0x100;
        private int lookahead;
        private int match_available;
        private int match_length;
        private int match_start;
        private int matches;
        private const int MAX_BITS = 15;
        private int max_chain_length;
        private int max_lazy_match;
        private const int MAX_MATCH = 0x102;
        private const int MAX_MEM_LEVEL = 9;
        private const int MAX_WBITS = 15;
        private byte method;
        private const int MIN_LOOKAHEAD = 0x106;
        private const int MIN_MATCH = 3;
        private const int NeedMore = 0;
        private int nice_match;
        internal int noheader;
        internal int opt_len;
        internal int pending;
        internal byte[] pending_buf;
        private int pending_buf_size;
        internal int pending_out;
        private const int PRESET_DICT = 0x20;
        private short[] prev;
        private int prev_length;
        private int prev_match;
        private const int REP_3_6 = 0x10;
        private const int REPZ_11_138 = 0x12;
        private const int REPZ_3_10 = 0x11;
        private const int SLOW = 2;
        internal int static_len;
        private const int STATIC_TREES = 1;
        private int status;
        private const int STORED = 0;
        private const int STORED_BLOCK = 0;
        private int strategy;
        private ZStream strm;
        private int strstart;
        private int w_bits;
        private int w_mask;
        private int w_size;
        private byte[] window;
        private int window_size;
        private const int Z_ASCII = 1;
        private const int Z_BINARY = 0;
        private const int Z_BUF_ERROR = -5;
        private const int Z_DATA_ERROR = -3;
        private const int Z_DEFAULT_STRATEGY = 0;
        internal const int Z_DEFLATED = 8;
        private static string[] z_errmsg = new string[] { "need dictionary", "stream end", string.Empty, "file error", "stream error", "data error", "insufficient memory", "buffer error", "incompatible version", string.Empty };
        private const int Z_FILTERED = 1;
        private const int Z_FINISH = 4;
        private const int Z_FULL_FLUSH = 3;
        private const int Z_HUFFMAN_ONLY = 2;
        private const int Z_NEED_DICT = 2;
        private const int Z_NO_FLUSH = 0;
        private const int Z_OK = 0;
        private const int Z_PARTIAL_FLUSH = 1;
        private const int Z_STREAM_END = 1;
        private const int Z_STREAM_ERROR = -2;
        private const int Z_UNKNOWN = 2;

        // Methods
        internal Deflate()
        {
        }

        private void _tr_align()
        {
            this.send_bits(2, 3);
            this.send_code(0x100, StaticTree.static_ltree);
            this.bi_flush();
            if ((((1 + this.last_eob_len) + 10) - this.bi_valid) < 9)
            {
                this.send_bits(2, 3);
                this.send_code(0x100, StaticTree.static_ltree);
                this.bi_flush();
            }
            this.last_eob_len = 7;
        }

        private void _tr_flush_block(int buf, int stored_len, bool eof)
        {
            int num;
            int num2;
            int num3 = 0;
            if (this.level > 0)
            {
                if (this.data_type == 2)
                {
                    this.set_data_type();
                }
                this.l_desc.build_tree(this);
                this.d_desc.build_tree(this);
                num3 = this.build_bl_tree();
                num = ((this.opt_len + 3) + 7) >> 3;
                num2 = ((this.static_len + 3) + 7) >> 3;
                if (num2 <= num)
                {
                    num = num2;
                }
            }
            else
            {
                num = num2 = stored_len + 5;
            }
            if (((stored_len + 4) <= num) && (buf != -1))
            {
                this._tr_stored_block(buf, stored_len, eof);
            }
            else if (num2 == num)
            {
                this.send_bits(2 + (eof ? 1 : 0), 3);
                this.compress_block(StaticTree.static_ltree, StaticTree.static_dtree);
            }
            else
            {
                this.send_bits(4 + (eof ? 1 : 0), 3);
                this.send_all_trees(this.l_desc.max_code + 1, this.d_desc.max_code + 1, num3 + 1);
                this.compress_block(this.dyn_ltree, this.dyn_dtree);
            }
            this.init_block();
            if (eof)
            {
                this.bi_windup();
            }
        }

        private void _tr_stored_block(int buf, int stored_len, bool eof)
        {
            this.send_bits(eof ? 1 : 0, 3);
            this.copy_block(buf, stored_len, true);
        }

        private bool _tr_tally(int dist, int lc)
        {
            this.pending_buf[this.d_buf + (this.last_lit * 2)] = (byte)(dist >> 8);
            this.pending_buf[(this.d_buf + (this.last_lit * 2)) + 1] = (byte)dist;
            this.pending_buf[this.l_buf + this.last_lit] = (byte)lc;
            this.last_lit++;
            if (dist == 0)
            {
                this.dyn_ltree[lc * 2] = (short)(this.dyn_ltree[lc * 2] + 1);
            }
            else
            {
                this.matches++;
                dist--;
                this.dyn_ltree[((Tree._length_code[lc] + 0x100) + 1) * 2] = (short)(this.dyn_ltree[((Tree._length_code[lc] + 0x100) + 1) * 2] + 1);
                this.dyn_dtree[Tree.d_code(dist) * 2] = (short)(this.dyn_dtree[Tree.d_code(dist) * 2] + 1);
            }
            if (((this.last_lit & 0x1fff) == 0) && (this.level > 2))
            {
                int num = this.last_lit * 8;
                int num2 = this.strstart - this.block_start;
                for (int i = 0; i < 30; i++)
                {
                    num += this.dyn_dtree[i * 2] * (((int)5L) + Tree.extra_dbits[i]);
                }
                num = num >> 3;
                if ((this.matches < (this.last_lit / 2)) && (num < (num2 / 2)))
                {
                    return true;
                }
            }
            return (this.last_lit == (this.lit_bufsize - 1));
        }

        private void bi_flush()
        {
            if (this.bi_valid == 0x10)
            {
                this.put_short(this.bi_buf);
                this.bi_buf = 0;
                this.bi_valid = 0;
            }
            else if (this.bi_valid >= 8)
            {
                this.put_byte((byte)this.bi_buf);
                this.bi_buf = (short)(this.bi_buf >> 8);
                this.bi_valid -= 8;
            }
        }

        private void bi_windup()
        {
            if (this.bi_valid > 8)
            {
                this.put_short(this.bi_buf);
            }
            else if (this.bi_valid > 0)
            {
                this.put_byte((byte)this.bi_buf);
            }
            this.bi_buf = 0;
            this.bi_valid = 0;
        }

        private int build_bl_tree()
        {
            this.scan_tree(this.dyn_ltree, this.l_desc.max_code);
            this.scan_tree(this.dyn_dtree, this.d_desc.max_code);
            this.bl_desc.build_tree(this);
            int index = 0x12;
            while (index >= 3)
            {
                if (this.bl_tree[(Tree.bl_order[index] * 2) + 1] != 0)
                {
                    break;
                }
                index--;
            }
            this.opt_len += (((3 * (index + 1)) + 5) + 5) + 4;
            return index;
        }

        private void compress_block(short[] ltree, short[] dtree)
        {
            int num3 = 0;
            if (this.last_lit != 0)
            {
                do
                {
                    int dist = ((this.pending_buf[this.d_buf + (num3 * 2)] << 8) & 0xff00) | (this.pending_buf[(this.d_buf + (num3 * 2)) + 1] & 0xff);
                    int c = this.pending_buf[this.l_buf + num3] & 0xff;
                    num3++;
                    if (dist == 0)
                    {
                        this.send_code(c, ltree);
                    }
                    else
                    {
                        int index = Tree._length_code[c];
                        this.send_code((index + 0x100) + 1, ltree);
                        int length = Tree.extra_lbits[index];
                        if (length != 0)
                        {
                            c -= Tree.base_length[index];
                            this.send_bits(c, length);
                        }
                        dist--;
                        index = Tree.d_code(dist);
                        this.send_code(index, dtree);
                        length = Tree.extra_dbits[index];
                        if (length != 0)
                        {
                            dist -= Tree.base_dist[index];
                            this.send_bits(dist, length);
                        }
                    }
                }
                while (num3 < this.last_lit);
            }
            this.send_code(0x100, ltree);
            this.last_eob_len = ltree[0x201];
        }

        private void copy_block(int buf, int len, bool header)
        {
            this.bi_windup();
            this.last_eob_len = 8;
            if (header)
            {
                this.put_short((short)len);
                this.put_short((short)~len);
            }
            this.put_byte(this.window, buf, len);
        }

        internal int deflate(ZStream strm, int flush)
        {
            if ((flush > 4) || (flush < 0))
            {
                return -2;
            }
            if (((strm.next_out == null) || ((strm.next_in == null) && (strm.avail_in != 0))) || ((this.status == 0x29a) && (flush != 4)))
            {
                strm.msg = z_errmsg[4];
                return -2;
            }
            if (strm.avail_out == 0)
            {
                strm.msg = z_errmsg[7];
                return -5;
            }
            this.strm = strm;
            int num = this.last_flush;
            this.last_flush = flush;
            if (this.status == 0x2a)
            {
                int b = (8 + ((this.w_bits - 8) << 4)) << 8;
                int num3 = ((this.level - 1) & 0xff) >> 1;
                if (num3 > 3)
                {
                    num3 = 3;
                }
                b |= num3 << 6;
                if (this.strstart != 0)
                {
                    b |= 0x20;
                }
                b += 0x1f - (b % 0x1f);
                this.status = 0x71;
                this.putShortMSB(b);
                if (this.strstart != 0)
                {
                    this.putShortMSB((int)(strm.adler >> 0x10));
                    this.putShortMSB((int)(strm.adler & 0xffffL));
                }
                strm.adler = strm._adler.checkSum(0L, null, 0, 0);
            }
            if (this.pending != 0)
            {
                strm.flush_pending();
                if (strm.avail_out == 0)
                {
                    this.last_flush = -1;
                    return 0;
                }
            }
            else if (((strm.avail_in == 0) && (flush <= num)) && (flush != 4))
            {
                return 1;
            }
            if ((this.status == 0x29a) && (strm.avail_in != 0))
            {
                strm.msg = z_errmsg[7];
                return -5;
            }
            if (((strm.avail_in != 0) || (this.lookahead != 0)) || ((flush != 0) && (this.status != 0x29a)))
            {
                int num4 = -1;
                switch (config_table[this.level].func)
                {
                    case 0:
                        num4 = this.deflate_stored(flush);
                        break;

                    case 1:
                        num4 = this.deflate_fast(flush);
                        break;

                    case 2:
                        num4 = this.deflate_slow(flush);
                        break;
                }
                switch (num4)
                {
                    case 2:
                    case 3:
                        this.status = 0x29a;
                        break;
                }
                if ((num4 == 0) || (num4 == 2))
                {
                    if (strm.avail_out == 0)
                    {
                        this.last_flush = -1;
                    }
                    return 0;
                }
                if (num4 == 1)
                {
                    if (flush == 1)
                    {
                        this._tr_align();
                    }
                    else
                    {
                        this._tr_stored_block(0, 0, false);
                        if (flush == 3)
                        {
                            for (int i = 0; i < this.hash_size; i++)
                            {
                                this.head[i] = 0;
                            }
                        }
                    }
                    strm.flush_pending();
                    if (strm.avail_out == 0)
                    {
                        this.last_flush = -1;
                        return 0;
                    }
                }
            }
            if (flush == 4)
            {
                if (this.noheader != 0)
                {
                    return 1;
                }
                this.putShortMSB((int)(strm.adler >> 0x10));
                this.putShortMSB((int)(strm.adler & 0xffffL));
                strm.flush_pending();
                this.noheader = -1;
                if (this.pending == 0)
                {
                    return 1;
                }
            }
            return 0;
        }

        private int deflate_fast(int flush)
        {
            bool flag;
            int num = 0;
        Label_0002:
            if (this.lookahead < 0x106)
            {
                this.fill_window();
                if ((this.lookahead < 0x106) && (flush == 0))
                {
                    return 0;
                }
                if (this.lookahead == 0)
                {
                    this.flush_block_only(flush == 4);
                    if (this.strm.avail_out == 0)
                    {
                        if (flush == 4)
                        {
                            return 2;
                        }
                        return 0;
                    }
                    if (flush != 4)
                    {
                        return 1;
                    }
                    return 3;
                }
            }
            if (this.lookahead >= 3)
            {
                this.ins_h = ((this.ins_h << this.hash_shift) ^ (this.window[this.strstart + 2] & 0xff)) & this.hash_mask;
                num = this.head[this.ins_h] & 0xffff;
                this.prev[this.strstart & this.w_mask] = this.head[this.ins_h];
                this.head[this.ins_h] = (short)this.strstart;
            }
            if (((num != 0L) && ((this.strstart - num) <= (this.w_size - 0x106))) && (this.strategy != 2))
            {
                this.match_length = this.longest_match(num);
            }
            if (this.match_length >= 3)
            {
                flag = this._tr_tally(this.strstart - this.match_start, this.match_length - 3);
                this.lookahead -= this.match_length;
                if ((this.match_length <= this.max_lazy_match) && (this.lookahead >= 3))
                {
                    this.match_length--;
                    do
                    {
                        this.strstart++;
                        this.ins_h = ((this.ins_h << this.hash_shift) ^ (this.window[this.strstart + 2] & 0xff)) & this.hash_mask;
                        num = this.head[this.ins_h] & 0xffff;
                        this.prev[this.strstart & this.w_mask] = this.head[this.ins_h];
                        this.head[this.ins_h] = (short)this.strstart;
                    }
                    while (--this.match_length != 0);
                    this.strstart++;
                }
                else
                {
                    this.strstart += this.match_length;
                    this.match_length = 0;
                    this.ins_h = this.window[this.strstart] & 0xff;
                    this.ins_h = ((this.ins_h << this.hash_shift) ^ (this.window[this.strstart + 1] & 0xff)) & this.hash_mask;
                }
            }
            else
            {
                flag = this._tr_tally(0, this.window[this.strstart] & 0xff);
                this.lookahead--;
                this.strstart++;
            }
            if (!flag)
            {
                goto Label_0002;
            }
            this.flush_block_only(false);
            if (this.strm.avail_out != 0)
            {
                goto Label_0002;
            }
            return 0;
        }

        private int deflate_slow(int flush)
        {
            bool flag;
            int num = 0;
            while (true)
            {
                if (this.lookahead < 0x106)
                {
                    this.fill_window();
                    if ((this.lookahead < 0x106) && (flush == 0))
                    {
                        return 0;
                    }
                    if (this.lookahead == 0)
                    {
                        break;
                    }
                }
                if (this.lookahead >= 3)
                {
                    this.ins_h = ((this.ins_h << this.hash_shift) ^ (this.window[this.strstart + 2] & 0xff)) & this.hash_mask;
                    num = this.head[this.ins_h] & 0xffff;
                    this.prev[this.strstart & this.w_mask] = this.head[this.ins_h];
                    this.head[this.ins_h] = (short)this.strstart;
                }
                this.prev_length = this.match_length;
                this.prev_match = this.match_start;
                this.match_length = 2;
                if (((num != 0) && (this.prev_length < this.max_lazy_match)) && ((this.strstart - num) <= (this.w_size - 0x106)))
                {
                    if (this.strategy != 2)
                    {
                        this.match_length = this.longest_match(num);
                    }
                    if ((this.match_length <= 5) && ((this.strategy == 1) || ((this.match_length == 3) && ((this.strstart - this.match_start) > 0x1000))))
                    {
                        this.match_length = 2;
                    }
                }
                if ((this.prev_length >= 3) && (this.match_length <= this.prev_length))
                {
                    int num2 = (this.strstart + this.lookahead) - 3;
                    flag = this._tr_tally((this.strstart - 1) - this.prev_match, this.prev_length - 3);
                    this.lookahead -= this.prev_length - 1;
                    this.prev_length -= 2;
                    do
                    {
                        if (++this.strstart <= num2)
                        {
                            this.ins_h = ((this.ins_h << this.hash_shift) ^ (this.window[this.strstart + 2] & 0xff)) & this.hash_mask;
                            num = this.head[this.ins_h] & 0xffff;
                            this.prev[this.strstart & this.w_mask] = this.head[this.ins_h];
                            this.head[this.ins_h] = (short)this.strstart;
                        }
                    }
                    while (--this.prev_length != 0);
                    this.match_available = 0;
                    this.match_length = 2;
                    this.strstart++;
                    if (flag)
                    {
                        this.flush_block_only(false);
                        if (this.strm.avail_out == 0)
                        {
                            return 0;
                        }
                    }
                }
                else if (this.match_available != 0)
                {
                    if (this._tr_tally(0, this.window[this.strstart - 1] & 0xff))
                    {
                        this.flush_block_only(false);
                    }
                    this.strstart++;
                    this.lookahead--;
                    if (this.strm.avail_out == 0)
                    {
                        return 0;
                    }
                }
                else
                {
                    this.match_available = 1;
                    this.strstart++;
                    this.lookahead--;
                }
            }
            if (this.match_available != 0)
            {
                flag = this._tr_tally(0, this.window[this.strstart - 1] & 0xff);
                this.match_available = 0;
            }
            this.flush_block_only(flush == 4);
            if (this.strm.avail_out == 0)
            {
                if (flush == 4)
                {
                    return 2;
                }
                return 0;
            }
            if (flush != 4)
            {
                return 1;
            }
            return 3;
        }

        private int deflate_stored(int flush)
        {
            int num = 0xffff;
            if (num > (this.pending_buf_size - 5))
            {
                num = this.pending_buf_size - 5;
            }
        Label_001A:
            if (this.lookahead <= 1)
            {
                this.fill_window();
                if ((this.lookahead == 0) && (flush == 0))
                {
                    return 0;
                }
                if (this.lookahead == 0)
                {
                    this.flush_block_only(flush == 4);
                    if (this.strm.avail_out == 0)
                    {
                        if (flush != 4)
                        {
                            return 0;
                        }
                        return 2;
                    }
                    if (flush != 4)
                    {
                        return 1;
                    }
                    return 3;
                }
            }
            this.strstart += this.lookahead;
            this.lookahead = 0;
            int num2 = this.block_start + num;
            if ((this.strstart == 0) || (this.strstart >= num2))
            {
                this.lookahead = this.strstart - num2;
                this.strstart = num2;
                this.flush_block_only(false);
                if (this.strm.avail_out == 0)
                {
                    return 0;
                }
            }
            if ((this.strstart - this.block_start) < (this.w_size - 0x106))
            {
                goto Label_001A;
            }
            this.flush_block_only(false);
            if (this.strm.avail_out != 0)
            {
                goto Label_001A;
            }
            return 0;
        }

        internal int deflateEnd()
        {
            if (((this.status != 0x2a) && (this.status != 0x71)) && (this.status != 0x29a))
            {
                return -2;
            }
            this.pending_buf = null;
            this.head = null;
            this.prev = null;
            this.window = null;
            if (this.status != 0x71)
            {
                return 0;
            }
            return -3;
        }

        internal int deflateInit(ZStream strm, int level, int bits)
        {
            return this.deflateInit2(strm, level, 8, bits, 8, 0);
        }

        private int deflateInit2(ZStream strm, int level, int method, int windowBits, int memLevel, int strategy)
        {
            int num = 0;
            strm.msg = null;
            if (level < 0)
            {
                level = 6;
            }
            if (windowBits < 0)
            {
                num = 1;
                windowBits = -windowBits;
            }
            if ((((memLevel < 1) || (memLevel > 9)) || ((method != 8) || (windowBits < 8))) || (((windowBits > 15) || (level < 0)) || (((level > 9) || (strategy < 0)) || (strategy > 2))))
            {
                return -2;
            }
            strm.dstate = this;
            this.noheader = num;
            this.w_bits = windowBits;
            this.w_size = ((int)1) << this.w_bits;
            this.w_mask = this.w_size - 1;
            this.hash_bits = memLevel + 7;
            this.hash_size = ((int)1) << this.hash_bits;
            this.hash_mask = this.hash_size - 1;
            this.hash_shift = ((this.hash_bits + 3) - 1) / 3;
            this.window = new byte[this.w_size * 2];
            this.prev = new short[this.w_size];
            this.head = new short[this.hash_size];
            this.lit_bufsize = ((int)1) << (memLevel + 6);
            this.pending_buf = new byte[this.lit_bufsize * 4];
            this.pending_buf_size = this.lit_bufsize * 4;
            this.d_buf = this.lit_bufsize / 2;
            this.l_buf = 3 * this.lit_bufsize;
            this.level = level;
            this.strategy = strategy;
            this.method = (byte)method;
            return this.deflateReset(strm);
        }

        internal int deflateParams(ZStream strm, int _level, int _strategy)
        {
            int num = 0;
            if (_level < 0)
            {
                _level = 6;
            }
            if (((_level < 0) || (_level > 9)) || ((_strategy < 0) || (_strategy > 2)))
            {
                return -2;
            }
            if ((config_table[this.level].func != config_table[_level].func) && (strm.total_in != 0L))
            {
                num = strm.deflate(1);
            }
            if (this.level != _level)
            {
                this.level = _level;
                this.max_lazy_match = config_table[this.level].max_lazy;
                this.good_match = config_table[this.level].good_length;
                this.nice_match = config_table[this.level].nice_length;
                this.max_chain_length = config_table[this.level].max_chain;
            }
            this.strategy = _strategy;
            return num;
        }

        private int deflateReset(ZStream strm)
        {
            strm.total_in = strm.total_out = 0L;
            strm.msg = null;
            strm.data_type = 2;
            this.pending = 0;
            this.pending_out = 0;
            if (this.noheader < 0)
            {
                this.noheader = 0;
            }
            this.status = (this.noheader != 0) ? 0x71 : 0x2a;
            strm.adler = strm._adler.checkSum(0L, null, 0, 0);
            this.last_flush = 0;
            this.tr_init();
            this.lm_init();
            return 0;
        }

        internal int deflateSetDictionary(ZStream strm, byte[] dictionary, int dictLength)
        {
            int length = dictLength;
            int sourceIndex = 0;
            if ((dictionary == null) || (this.status != 0x2a))
            {
                return -2;
            }
            strm.adler = strm._adler.checkSum(strm.adler, dictionary, 0, dictLength);
            if (length >= 3)
            {
                if (length > (this.w_size - 0x106))
                {
                    length = this.w_size - 0x106;
                    sourceIndex = dictLength - length;
                }
                Array.Copy(dictionary, sourceIndex, this.window, 0, length);
                this.strstart = length;
                this.block_start = length;
                this.ins_h = this.window[0] & 0xff;
                this.ins_h = ((this.ins_h << this.hash_shift) ^ (this.window[1] & 0xff)) & this.hash_mask;
                for (int i = 0; i <= (length - 3); i++)
                {
                    this.ins_h = ((this.ins_h << this.hash_shift) ^ (this.window[i + 2] & 0xff)) & this.hash_mask;
                    this.prev[i & this.w_mask] = this.head[this.ins_h];
                    this.head[this.ins_h] = (short)i;
                }
            }
            return 0;
        }

        private void fill_window()
        {
            int num;
            int num4;
        Label_0000:
            num4 = (this.window_size - this.lookahead) - this.strstart;
            if (((num4 == 0) && (this.strstart == 0)) && (this.lookahead == 0))
            {
                num4 = this.w_size;
            }
            else if (num4 == -1)
            {
                num4--;
            }
            else if (this.strstart >= ((this.w_size + this.w_size) - 0x106))
            {
                int num2;
                Array.Copy(this.window, this.w_size, this.window, 0, this.w_size);
                this.match_start -= this.w_size;
                this.strstart -= this.w_size;
                this.block_start -= this.w_size;
                num = this.hash_size;
                int index = num;
                do
                {
                    num2 = this.head[--index] & 0xffff;
                    this.head[index] = (num2 >= this.w_size) ? ((short)(num2 - this.w_size)) : ((short)0);
                }
                while (--num != 0);
                num = this.w_size;
                index = num;
                do
                {
                    num2 = this.prev[--index] & 0xffff;
                    this.prev[index] = (num2 >= this.w_size) ? ((short)(num2 - this.w_size)) : ((short)0);
                }
                while (--num != 0);
                num4 += this.w_size;
            }
            if (this.strm.avail_in != 0)
            {
                num = this.strm.read_buf(this.window, this.strstart + this.lookahead, num4);
                this.lookahead += num;
                if (this.lookahead >= 3)
                {
                    this.ins_h = this.window[this.strstart] & 0xff;
                    this.ins_h = ((this.ins_h << this.hash_shift) ^ (this.window[this.strstart + 1] & 0xff)) & this.hash_mask;
                }
                if ((this.lookahead < 0x106) && (this.strm.avail_in != 0))
                {
                    goto Label_0000;
                }
            }
        }

        private void flush_block_only(bool eof)
        {
            this._tr_flush_block((this.block_start >= 0) ? this.block_start : -1, this.strstart - this.block_start, eof);
            this.block_start = this.strstart;
            this.strm.flush_pending();
        }

        private void init_block()
        {
            for (int i = 0; i < 0x11e; i++)
            {
                this.dyn_ltree[i * 2] = 0;
            }
            for (int j = 0; j < 30; j++)
            {
                this.dyn_dtree[j * 2] = 0;
            }
            for (int k = 0; k < 0x13; k++)
            {
                this.bl_tree[k * 2] = 0;
            }
            this.dyn_ltree[0x200] = 1;
            this.opt_len = this.static_len = 0;
            this.last_lit = this.matches = 0;
        }

        private void lm_init()
        {
            this.window_size = 2 * this.w_size;
            this.head[this.hash_size - 1] = 0;
            for (int i = 0; i < (this.hash_size - 1); i++)
            {
                this.head[i] = 0;
            }
            this.max_lazy_match = config_table[this.level].max_lazy;
            this.good_match = config_table[this.level].good_length;
            this.nice_match = config_table[this.level].nice_length;
            this.max_chain_length = config_table[this.level].max_chain;
            this.strstart = 0;
            this.block_start = 0;
            this.lookahead = 0;
            this.match_length = this.prev_length = 2;
            this.match_available = 0;
            this.ins_h = 0;
        }

        private int longest_match(int cur_match)
        {
            int num = this.max_chain_length;
            int strstart = this.strstart;
            int num5 = this.prev_length;
            int num6 = (this.strstart > (this.w_size - 0x106)) ? (this.strstart - (this.w_size - 0x106)) : 0;
            int lookahead = this.nice_match;
            int num8 = this.w_mask;
            int num9 = this.strstart + 0x102;
            byte num10 = this.window[(strstart + num5) - 1];
            byte num11 = this.window[strstart + num5];
            if (this.prev_length >= this.good_match)
            {
                num = num >> 2;
            }
            if (lookahead > this.lookahead)
            {
                lookahead = this.lookahead;
            }
            do
            {
                int index = cur_match;
                if (((this.window[index + num5] == num11) && (this.window[(index + num5) - 1] == num10)) && ((this.window[index] == this.window[strstart]) && (this.window[++index] == this.window[strstart + 1])))
                {
                    strstart += 2;
                    index++;
                    while ((((this.window[++strstart] == this.window[++index]) && (this.window[++strstart] == this.window[++index])) && ((this.window[++strstart] == this.window[++index]) && (this.window[++strstart] == this.window[++index]))) && ((((this.window[++strstart] == this.window[++index]) && (this.window[++strstart] == this.window[++index])) && ((this.window[++strstart] == this.window[++index]) && (this.window[++strstart] == this.window[++index]))) && ((strstart < num9) && (index < num9))))
                    {
                    }
                    int num4 = 0x102 - (num9 - strstart);
                    strstart = num9 - 0x102;
                    if (num4 > num5)
                    {
                        this.match_start = cur_match;
                        num5 = num4;
                        if (num4 >= lookahead)
                        {
                            break;
                        }
                        num10 = this.window[(strstart + num5) - 1];
                        num11 = this.window[strstart + num5];
                    }
                }
            }
            while (((cur_match = this.prev[cur_match & num8] & 0xffff) > num6) && (--num != 0));
            if (num5 <= this.lookahead)
            {
                return num5;
            }
            return this.lookahead;
        }

        internal void pqdownheap(short[] tree, int k)
        {
            int n = this.heap[k];
            for (int i = k << 1; i <= this.heap_len; i = i << 1)
            {
                if ((i < this.heap_len) && smaller(tree, this.heap[i + 1], this.heap[i], this.depth))
                {
                    i++;
                }
                if (smaller(tree, n, this.heap[i], this.depth))
                {
                    break;
                }
                this.heap[k] = this.heap[i];
                k = i;
            }
            this.heap[k] = n;
        }

        private void put_byte(byte c)
        {
            this.pending_buf[this.pending++] = c;
        }

        private void put_byte(byte[] p, int start, int len)
        {
            Array.Copy(p, start, this.pending_buf, this.pending, len);
            this.pending += len;
        }

        private void put_short(int w)
        {
            this.put_byte((byte)w);
            this.put_byte((byte)(w >> 8));
        }

        private void putShortMSB(int b)
        {
            this.put_byte((byte)(b >> 8));
            this.put_byte((byte)b);
        }

        private void scan_tree(short[] tree, int max_code)
        {
            int num2 = -1;
            int num4 = tree[1];
            int num5 = 0;
            int num6 = 7;
            int num7 = 4;
            if (num4 == 0)
            {
                num6 = 0x8a;
                num7 = 3;
            }
            tree[((max_code + 1) * 2) + 1] = -1;
            for (int i = 0; i <= max_code; i++)
            {
                int num3 = num4;
                num4 = tree[((i + 1) * 2) + 1];
                if ((++num5 >= num6) || (num3 != num4))
                {
                    if (num5 < num7)
                    {
                        this.bl_tree[num3 * 2] = (short)(this.bl_tree[num3 * 2] + ((short)num5));
                    }
                    else if (num3 != 0)
                    {
                        if (num3 != num2)
                        {
                            this.bl_tree[num3 * 2] = (short)(this.bl_tree[num3 * 2] + 1);
                        }
                        this.bl_tree[0x20] = (short)(this.bl_tree[0x20] + 1);
                    }
                    else if (num5 <= 10)
                    {
                        this.bl_tree[0x22] = (short)(this.bl_tree[0x22] + 1);
                    }
                    else
                    {
                        this.bl_tree[0x24] = (short)(this.bl_tree[0x24] + 1);
                    }
                    num5 = 0;
                    num2 = num3;
                    if (num4 == 0)
                    {
                        num6 = 0x8a;
                        num7 = 3;
                    }
                    else if (num3 == num4)
                    {
                        num6 = 6;
                        num7 = 3;
                    }
                    else
                    {
                        num6 = 7;
                        num7 = 4;
                    }
                }
            }
        }

        private void send_all_trees(int lcodes, int dcodes, int blcodes)
        {
            this.send_bits(lcodes - 0x101, 5);
            this.send_bits(dcodes - 1, 5);
            this.send_bits(blcodes - 4, 4);
            for (int i = 0; i < blcodes; i++)
            {
                this.send_bits(this.bl_tree[(Tree.bl_order[i] * 2) + 1], 3);
            }
            this.send_tree(this.dyn_ltree, lcodes - 1);
            this.send_tree(this.dyn_dtree, dcodes - 1);
        }

        private void send_bits(int value, int length)
        {
            int num = length;
            if (this.bi_valid > (0x10 - num))
            {
                int num2 = value;
                this.bi_buf = (short)(((ushort)this.bi_buf) | ((ushort)((num2 << this.bi_valid) & 0xffff)));
                this.put_short(this.bi_buf);
                this.bi_buf = (short)(num2 >> (0x10 - this.bi_valid));
                this.bi_valid += num - 0x10;
            }
            else
            {
                this.bi_buf = (short)(((ushort)this.bi_buf) | ((ushort)((value << this.bi_valid) & 0xffff)));
                this.bi_valid += num;
            }
        }

        private void send_code(int c, short[] tree)
        {
            this.send_bits(tree[c * 2] & 0xffff, tree[(c * 2) + 1] & 0xffff);
        }

        private void send_tree(short[] tree, int max_code)
        {
            int num2 = -1;
            int num4 = tree[1];
            int num5 = 0;
            int num6 = 7;
            int num7 = 4;
            if (num4 == 0)
            {
                num6 = 0x8a;
                num7 = 3;
            }
            for (int i = 0; i <= max_code; i++)
            {
                int c = num4;
                num4 = tree[((i + 1) * 2) + 1];
                if ((++num5 >= num6) || (c != num4))
                {
                    if (num5 < num7)
                    {
                        do
                        {
                            this.send_code(c, this.bl_tree);
                        }
                        while (--num5 != 0);
                    }
                    else if (c != 0)
                    {
                        if (c != num2)
                        {
                            this.send_code(c, this.bl_tree);
                            num5--;
                        }
                        this.send_code(0x10, this.bl_tree);
                        this.send_bits(num5 - 3, 2);
                    }
                    else if (num5 <= 10)
                    {
                        this.send_code(0x11, this.bl_tree);
                        this.send_bits(num5 - 3, 3);
                    }
                    else
                    {
                        this.send_code(0x12, this.bl_tree);
                        this.send_bits(num5 - 11, 7);
                    }
                    num5 = 0;
                    num2 = c;
                    if (num4 == 0)
                    {
                        num6 = 0x8a;
                        num7 = 3;
                    }
                    else if (c == num4)
                    {
                        num6 = 6;
                        num7 = 3;
                    }
                    else
                    {
                        num6 = 7;
                        num7 = 4;
                    }
                }
            }
        }

        private void set_data_type()
        {
            int num = 0;
            int num2 = 0;
            int num3 = 0;
            while (num < 7)
            {
                num3 += this.dyn_ltree[num * 2];
                num++;
            }
            while (num < 0x80)
            {
                num2 += this.dyn_ltree[num * 2];
                num++;
            }
            while (num < 0x100)
            {
                num3 += this.dyn_ltree[num * 2];
                num++;
            }
            this.data_type = (num3 > (num2 >> 2)) ? ((byte)0) : ((byte)1);
        }

        private static bool smaller(short[] tree, int n, int m, byte[] depth)
        {
            return ((tree[n * 2] < tree[m * 2]) || ((tree[n * 2] == tree[m * 2]) && (depth[n] <= depth[m])));
        }

        private void tr_init()
        {
            this.l_desc.dyn_tree = this.dyn_ltree;
            this.l_desc.stat_desc = StaticTree.static_l_desc;
            this.d_desc.dyn_tree = this.dyn_dtree;
            this.d_desc.stat_desc = StaticTree.static_d_desc;
            this.bl_desc.dyn_tree = this.bl_tree;
            this.bl_desc.stat_desc = StaticTree.static_bl_desc;
            this.bi_buf = 0;
            this.bi_valid = 0;
            this.last_eob_len = 8;
            this.init_block();
        }

        // Nested Types
        private class Config
        {
            // Fields
            internal int func;
            internal int good_length;
            internal int max_chain;
            internal int max_lazy;
            internal int nice_length;

            // Methods
            internal Config(int good_length, int max_lazy, int nice_length, int max_chain, int func)
            {
                this.good_length = good_length;
                this.max_lazy = max_lazy;
                this.nice_length = nice_length;
                this.max_chain = max_chain;
                this.func = func;
            }
        }
    }
}
